#Replication file for Dowling/Miller/Morris
#"Crossover Voting Rates in Partisan and Nonpartisan Elections: Evidence from Cast Vote Records"
#Political Research Quarterly 2025

#Before running this file, load the "cvr_replication.RData" Workspace

#This workspace contains five data frames. 
#data is the master file containing all votes. 
#substate.data contains the votes for all sub-state elections in the 2018 election that we use for this analysis
#substate.data.DR contains the same data from substate.data, but only from races that feature one Democrat running against one Republican
#substate.model.contested is a version of substate.data that is model ready 
#(i.e., it contains votes from contested races and appropriate variable factorization)
#substate.model.contested.perfect filters substate.model.contested to votes from 
#voters with "perfect" (straight D or straight R) partisan scores

#Load packages
library(tidyverse)
library(ggpubr)
library(ggeffects)
library(dotwhisker)
library(gapminder)
library(stargazer)
library(plm)
library(broom)
library(sandwich)
library(lmtest)
library(scales)
library(xtable)
library(lmtest)
library(multiwayvcov)

options(scipen=999)

#We first provide replication in the order that figures/tables appear in the paper
#for all tables and figures that do not involve regression modeling. We replicate
#the regression results in a labeled section below (see line 945).

############################################

#We first Validate party estimate measure, by correlating estimate and actual 
#for sub-state partisan candidates. This supports an important claim we make in the data

p.cor <- substate.data %>%
  filter(str_detect(code, "_pt_")==TRUE) %>%
  select(code, clean_last, p_vote, np_party_est) %>%
  distinct()

#Correlation
cor(p.cor$p_vote, p.cor$np_party_est, use="complete.obs")
#.91

rm(p.cor)

############################################

#Figure 1: Percent of Crossover Votes Cast by Partisan Voters in Sub-State Partisan and
#Nonpartisan Elections, by Office and Contestedness

###Votes cast by partisan voters in partisan races, OVERALL
np <- substate.data %>%
  filter(partisan==1)
#Obs provide the count

#Calculate Rate By Office
p.overall.means <- np %>% 
  group_by(clean_code) %>%
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  ungroup() %>%
  mutate(contested="All Races")

###Votes cast by partisan voters in partisan races CONTESTED BY AT LEAST 2 CANDIDATES
np <- substate.data %>%
  filter(can_count > 1 & partisan==1)
#Obs provide the count

#Calculate Rate By Office
p.contested.means <- np %>% 
  group_by(clean_code) %>%
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  ungroup() %>%
  mutate(contested="Contested by at Least 2 Cands.")

###Votes cast by partisan voters in partisan races CONTESTED BY AT LEAST 1 D and 1 R
np <- substate.data %>%
  filter(partisan==1) %>%
  filter(num_dem>=1 & num_rep>=1)
#Obs provide the count

#Calculate Rate By Office
p.p.contested.means <- np %>% 
  group_by(clean_code) %>%
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  ungroup() %>%
  mutate(contested="Contested by at least 1 D and 1 R")

###Votes cast by partisan voters in partisan races CONTESTED BY EXACTLY 1 D and 1 R
np <- substate.data.DR %>%
  filter(partisan==1)
#Obs provide the count

#Calculate Rate By Office
p.dr.means <- np %>% 
  group_by(clean_code) %>%
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  ungroup() %>%
  mutate(contested="Party-Contested")

#Combine partisan means into one data frame
contested.means<-rbind(p.overall.means, p.dr.means)
contested.means<- contested.means %>%
  mutate(contested=factor(contested, levels=c("All Races",
                                              "Party-Contested")))

#Draw the Partisan figure
p.panel <- ggplot(contested.means, aes(x = clean_code, y = ic, fill=contested)) +
  geom_bar(stat = "identity",
           alpha=0.5, position="dodge") +
  scale_fill_manual(name="Partisan Race Status", values = c("blue", "red")) +
  xlab("") +
  scale_x_discrete(labels=c("Auditor", "Board\n of Ed.", "Clerk", "Coroner", "County\n Council", 
                            "Probate\n  Judge", "Registrar\n  of Deeds", 
                            "Sheriff", "Solicitor", "Supervisor", 
                            "Treasurer")) +
  scale_y_continuous(limits=c(0,.7), breaks=c(.1,.2,.3,.4,.5,.6,.7), labels = scales::percent_format(accuracy = 1)) +
  ylab("Perc. Crossover Voting") +
  ggtitle("County/Local Partisan Elections") +
  theme_bw() +
  theme(text=element_text(size=15), legend.position="bottom")


###Non-Partisan Pane:

###Votes cast by partisan voters in non-partisan races, OVERALL
np <- substate.data %>%
  filter(partisan==0)
#Obs provide the count

#Calculate Rate By Office
p.overall.means <- np %>% 
  group_by(clean_code) %>%
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  ungroup() %>%
  mutate(contested="All Races")

###Votes cast by partisan voters in partisan races CONTESTED BY AT LEAST 2 CANDIDATES
np <- substate.data %>%
  filter(can_count > 1 & partisan==0)
#Obs provide the count

#Calculate Rate By Office
p.contested.means <- np %>% 
  group_by(clean_code) %>%
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  ungroup() %>%
  mutate(contested="Contested by at Least 2 Cands.")

###Votes cast by partisan voters in partisan races CONTESTED BY AT LEAST 1 D and 1 R
np <- substate.data %>%
  filter(partisan==0) %>%
  filter(num_dem >= 1 & num_rep >= 1)
#Obs provide the count

#Calculate Rate By Office
p.p.contested.means <- np %>% 
  group_by(clean_code) %>%
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  ungroup() %>%
  mutate(contested="Contested by at least 1 D and 1 R")

###Votes cast by partisan voters in partisan races CONTESTED BY EXACTLY 1 D and 1 R
np <- substate.data.DR %>%
  filter(partisan==0)
#Obs provide the count

#Calculate Rate By Office
p.dr.means <- np %>% 
  group_by(clean_code) %>%
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  ungroup() %>%
  mutate(contested="Contested by 1 D and 1 R")

#Combine partisan means into one data frame
contested.means<-rbind(p.overall.means, p.contested.means, p.p.contested.means, p.dr.means)
contested.means<- contested.means %>%
  mutate(contested=factor(contested, levels=c("All Races",
                                              "Contested by at Least 2 Cands.",
                                              "Contested by at least 1 D and 1 R",
                                              "Contested by 1 D and 1 R")))



#Draw the Non-Partisan figure
np.panel <- ggplot(contested.means, aes(x = clean_code, y = ic, fill=contested)) +
  geom_bar(stat = "identity",
           alpha=0.5, position="dodge") +
  scale_fill_manual(name="Non-Partisan Race Status", values = c("blue", "purple", "red", "green"),
                    guide=ggplot2::guide_legend(ncol = 2)) +
  xlab("") +
  scale_x_discrete(labels=c("Board\n of Ed.", "County\n Council", "Fire\n Dist.", "Mayor", 
                            "Public\n  Service\n  Dist.", "Public\n  Works\n Com.", 
                            "Soil and\n Water\n  Dist.", "Town\n Council", 
                            "Watershed\n Cons. Dist.")) +
  scale_y_continuous(limits=c(0,.7), breaks=c(.1,.2,.3,.4,.5,.6,.7), labels = scales::percent_format(accuracy = 1)) +
  ylab("Perc. Crossover Voting") +
  ggtitle("County/Local Non-Partisan Elections") +
  theme_bw() +
  theme(text=element_text(size=15), legend.position="bottom") 

ggarrange(p.panel, np.panel,
          ncol = 1, nrow = 2,
          common.legend = FALSE)

rm(np, contested.means, np.panel, p.contested.means, p.dr.means, p.overall.means, 
   p.p.contested.means, p.panel)

############################################

#Figure 2: Percent of Crossover Votes Cast by Partisan Voters in Sub-State Nonpartisan
#Elections, by Number of Candidates

#####Votes cast by partisan voters in partisan races CONTESTED BY EXACTLY 1 D and 2 R

#Republican Voter
np <- substate.data %>%
  filter(partisan==0) %>%
  filter(republican==1) %>%
  filter(num_dem==1 & num_rep==2)
#Obs provide the count

#Calculate Rate By Office
r.1.d.2.r <- np %>% 
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  mutate(contested="1 Dem. / 2 Rep.",
         voter_p="Rep.")

#Democratic Voter
np <- substate.data %>%
  filter(partisan==0) %>%
  filter(republican==0) %>%
  filter(num_dem==1 & num_rep==2)
#Obs provide the count

#Calculate Rate By Office
d.1.d.2.r <- np %>% 
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  mutate(contested="1 Dem. / 2 Rep.",
         voter_p="Dem.")

#####Votes cast by partisan voters in partisan races CONTESTED BY EXACTLY 1 R and 2 D

#Republican Voter
np <- substate.data %>%
  filter(partisan==0) %>%
  filter(republican==1) %>%
  filter(num_dem==2 & num_rep==1)
#Obs provide the count

#Calculate Rate By Office
r.2.d.1.r <- np %>% 
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  mutate(contested="2 Dem. / 1 Rep.",
         voter_p="Rep.")

#Democratic Voter
np <- substate.data %>%
  filter(partisan==0) %>%
  filter(republican==0) %>%
  filter(num_dem==2 & num_rep==1)
#Obs provide the count

#Calculate Rate By Office
d.2.d.1.r <- np %>% 
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  mutate(contested="2 Dem. / 1 Rep.",
         voter_p="Dem.")

#####Votes cast by partisan voters in partisan races CONTESTED BY EXACTLY 1 R and 1 D

#Republican Voter
np <- substate.data.DR %>%
  filter(partisan==0) %>%
  filter(republican==1) 
#Obs provide the count

#Calculate Rate By Office
r.1.d.1.r <- np %>% 
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  mutate(contested="1 Dem. / 1 Rep.",
         voter_p="Rep.")

#Democratic Voter
np <- substate.data.DR %>%
  filter(partisan==0) %>%
  filter(republican==0) 
#Obs provide the count

#Calculate Rate By Office
d.1.d.1.r <- np %>% 
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  mutate(contested="1 Dem. / 1 Rep.",
         voter_p="Dem.")


#Combine partisan means into one data frame
contested.means<-rbind(r.1.d.2.r, r.2.d.1.r,
                       r.1.d.1.r, d.1.d.1.r,
                       d.1.d.2.r, d.2.d.1.r)
contested.means<- contested.means %>%
  mutate(contested=factor(contested, levels=c("1 Dem. / 2 Rep.",
                                              "1 Dem. / 1 Rep.",
                                              "2 Dem. / 1 Rep."
  )))

#Draw the figure
ggplot(contested.means, aes(x = contested, y = ic, fill=voter_p)) +
  geom_bar(stat = "identity",
           alpha=0.7, position="dodge") +
  scale_fill_manual(name="Voter Party", values = c("blue", "red")) +
  xlab("") +
  #scale_y_continuous(limits=c(0,.52), breaks=c(.1,.2,.3,.4,.5,.6), labels = scales::percent_format(accuracy = 1)) +
  ylab("Perc. Crossover Voting") +
  #ggtitle("County/Local Partisan Elections") +
  theme_bw() +
  theme(text=element_text(size=15), legend.position="bottom")


rm(np, contested.means, d.1.d.2.r, d.2.d.1.r, r.1.d.2.r, r.2.d.1.r, d.1.d.1.r, r.1.d.1.r)

############################################

#Figure 3: Percent of Crossover Votes Cast by Partisan Voters in Sub-State Partisan and
#Nonpartisan Elections Contested by One Democrat and One Republican, by Voter Party
#Loyalty Score

#Create means of incorrect voting, binned by party loyalty
#This figure restricted to 1 D/1 R races

#For partisan races

voter.master <- substate.data.DR %>%
  filter(partisan==1) %>%
  mutate(loyalty_factor=ifelse(p_ip_fedstate == -1, "-1", ""),
         loyalty_factor=ifelse(p_ip_fedstate > -1 & p_ip_fedstate <= -0.75, "-.99- -.75", loyalty_factor),
         loyalty_factor=ifelse(p_ip_fedstate > -0.75 & p_ip_fedstate <= -0.5, "-.74- -.51", loyalty_factor),
         loyalty_factor=ifelse(p_ip_fedstate > -0.5 & p_ip_fedstate <= -0.25, "-.49- -.25", loyalty_factor),
         loyalty_factor=ifelse(p_ip_fedstate > -0.25 & p_ip_fedstate < 0, "-.24- -.01", loyalty_factor),
         loyalty_factor=ifelse(p_ip_fedstate > 0 & p_ip_fedstate <= 0.25, ".01-.24", loyalty_factor),
         loyalty_factor=ifelse(p_ip_fedstate > 0.25 & p_ip_fedstate <= 0.50, ".25-.49", loyalty_factor),
         loyalty_factor=ifelse(p_ip_fedstate > 0.50 & p_ip_fedstate <= 0.75, ".5-.74", loyalty_factor),
         loyalty_factor=ifelse(p_ip_fedstate > 0.75 & p_ip_fedstate < 1, ".75-.99", loyalty_factor),
         loyalty_factor=ifelse(p_ip_fedstate == 1, "1", loyalty_factor),
         loyalty_factor=factor(loyalty_factor, levels = c("-1", "-.99- -.75", "-.74- -.51", "-.49- -.25",
                                                          "-.24- -.01", ".01-.24", ".25-.49", 
                                                          ".5-.74", ".75-.99", "1"), ordered = TRUE))

m <- tapply(voter.master$incorrect_v, voter.master$loyalty_factor, mean)
sd <- tapply(voter.master$incorrect_v, voter.master$loyalty_factor, sd)
df <- data.frame(mean.y = m, sd = sd, bin = names(m))

df <- df %>%
  mutate(bin=factor(bin, levels = c("-1", "-.99- -.75", "-.74- -.51", "-.49- -.25",
                                    "-.24- -.01", ".01-.24", ".25-.49", 
                                    ".5-.74", ".75-.99", "1"), ordered = TRUE))

p<-ggplot(df, aes(x = bin, y = mean.y)) +
  geom_col() +
  xlab("Voter Party Loyalty Score") +
  ylab("Perc. Crossover Voting") +
  ggtitle("Partisan Elections") +
  theme_bw() +
  theme(text=element_text(size=15))

#Non-Partisan
voter.master <- substate.data.DR %>%
  filter(partisan==0) %>%
  mutate(loyalty_factor=ifelse(p_ip_fedstate == -1, "-1", ""),
         loyalty_factor=ifelse(p_ip_fedstate > -1 & p_ip_fedstate <= -0.75, "-.99- -.75", loyalty_factor),
         loyalty_factor=ifelse(p_ip_fedstate > -0.75 & p_ip_fedstate <= -0.5, "-.74- -.51", loyalty_factor),
         loyalty_factor=ifelse(p_ip_fedstate > -0.5 & p_ip_fedstate <= -0.25, "-.49- -.25", loyalty_factor),
         loyalty_factor=ifelse(p_ip_fedstate > -0.25 & p_ip_fedstate < 0, "-.24- -.01", loyalty_factor),
         loyalty_factor=ifelse(p_ip_fedstate > 0 & p_ip_fedstate <= 0.25, ".01-.24", loyalty_factor),
         loyalty_factor=ifelse(p_ip_fedstate > 0.25 & p_ip_fedstate <= 0.50, ".25-.49", loyalty_factor),
         loyalty_factor=ifelse(p_ip_fedstate > 0.50 & p_ip_fedstate <= 0.75, ".5-.74", loyalty_factor),
         loyalty_factor=ifelse(p_ip_fedstate > 0.75 & p_ip_fedstate < 1, ".75-.99", loyalty_factor),
         loyalty_factor=ifelse(p_ip_fedstate == 1, "1", loyalty_factor),
         loyalty_factor=factor(loyalty_factor, levels = c("-1", "-.99- -.75", "-.74- -.51", "-.49- -.25",
                                                          "-.24- -.01", ".01-.24", ".25-.49", 
                                                          ".5-.74", ".75-.99", "1"), ordered = TRUE))

m <- tapply(voter.master$incorrect_v, voter.master$loyalty_factor, mean)
sd <- tapply(voter.master$incorrect_v, voter.master$loyalty_factor, sd)
df <- data.frame(mean.y = m, sd = sd, bin = names(m))

df <- df %>%
  mutate(bin=factor(bin, levels = c("-1", "-.99- -.75", "-.74- -.51", "-.49- -.25",
                                    "-.24- -.01", ".01-.24", ".25-.49", 
                                    ".5-.74", ".75-.99", "1"), ordered = TRUE))

np<-ggplot(df, aes(x = bin, y = mean.y)) +
  geom_col() +
  xlab("Voter Party Loyalty Score") +
  ylab("Perc. Crossover Voting") +
  ggtitle("Non-Partisan Elections") +
  theme_bw() +
  theme(text=element_text(size=15))

ggarrange(p, np,
          ncol = 1, nrow = 2,
          common.legend = FALSE)

rm(df, p, m, sd, voter.master)

############################################

#Figure A1: Count of Voters in Party Loyalty Score Bins

voter.master <- substate.data.DR %>%
  mutate(counter=1,
         loyalty_factor=ifelse(p_ip_fedstate == -1, "-1", ""),
         loyalty_factor=ifelse(p_ip_fedstate > -1 & p_ip_fedstate <= -0.75, "-.99- -.75", loyalty_factor),
         loyalty_factor=ifelse(p_ip_fedstate > -0.75 & p_ip_fedstate <= -0.5, "-.74- -.51", loyalty_factor),
         loyalty_factor=ifelse(p_ip_fedstate > -0.5 & p_ip_fedstate <= -0.25, "-.49- -.25", loyalty_factor),
         loyalty_factor=ifelse(p_ip_fedstate > -0.25 & p_ip_fedstate < 0, "-.24- -.01", loyalty_factor),
         loyalty_factor=ifelse(p_ip_fedstate > 0 & p_ip_fedstate <= 0.25, ".01-.24", loyalty_factor),
         loyalty_factor=ifelse(p_ip_fedstate > 0.25 & p_ip_fedstate <= 0.50, ".25-.49", loyalty_factor),
         loyalty_factor=ifelse(p_ip_fedstate > 0.50 & p_ip_fedstate <= 0.75, ".5-.74", loyalty_factor),
         loyalty_factor=ifelse(p_ip_fedstate > 0.75 & p_ip_fedstate < 1, ".75-.99", loyalty_factor),
         loyalty_factor=ifelse(p_ip_fedstate == 1, "1", loyalty_factor),
         loyalty_factor=factor(loyalty_factor, levels = c("-1", "-.99- -.75", "-.74- -.51", "-.49- -.25",
                                                          "-.24- -.01", ".01-.24", ".25-.49", 
                                                          ".5-.74", ".75-.99", "1"), ordered = TRUE)) %>%
  group_by(loyalty_factor) %>%
  count(counter) %>%
  select(-counter)


ggplot(voter.master, aes(x = loyalty_factor, y = n)) +
  geom_col() +
  xlab("Voter Party Loyalty Score") +
  scale_y_continuous(labels = label_comma()) +
  ylab("Count of Voters") +
  theme_bw() +
  theme(text=element_text(size=15))

rm(voter.master)

############################################

#Figure A2: Percentage of Partisan and Nonpartisan Candidates in Party Loyalty Score Bins

#Candidates in Partisan Races
can.master <- data %>%
  filter(str_detect(code, "_pt_")==TRUE & is.na(np_score)==FALSE) %>%
  select(code, clean_last, np_matches, np_score) %>%
  distinct() %>%
  mutate(counter=1,
         loyalty_factor=ifelse(np_score == -1, "-1", ""),
         loyalty_factor=ifelse(np_score > -1 & np_score <= -0.75, "-.99- -.75", loyalty_factor),
         loyalty_factor=ifelse(np_score > -0.75 & np_score <= -0.5, "-.74- -.51", loyalty_factor),
         loyalty_factor=ifelse(np_score > -0.5 & np_score <= -0.25, "-.49- -.25", loyalty_factor),
         loyalty_factor=ifelse(np_score > -0.25 & np_score < 0, "-.24- -.01", loyalty_factor),
         loyalty_factor=ifelse(np_score ==0, "0", loyalty_factor),
         loyalty_factor=ifelse(np_score > 0 & np_score <= 0.25, ".01-.24", loyalty_factor),
         loyalty_factor=ifelse(np_score > 0.25 & np_score <= 0.50, ".25-.49", loyalty_factor),
         loyalty_factor=ifelse(np_score > 0.50 & np_score <= 0.75, ".5-.74", loyalty_factor),
         loyalty_factor=ifelse(np_score > 0.75 & np_score < 1, ".75-.99", loyalty_factor),
         loyalty_factor=ifelse(np_score == 1, "1", loyalty_factor),
         loyalty_factor=factor(loyalty_factor, levels = c("-1", "-.99- -.75", "-.74- -.51", "-.49- -.25",
                                                          "-.24- -.01", "0", ".01-.24", ".25-.49", 
                                                          ".5-.74", ".75-.99", "1"), ordered = TRUE)) %>%
  group_by(loyalty_factor) %>%
  count(counter)  %>%
  ungroup()


sum<-sum(can.master$n)
can.master$percent <- 100* (can.master$n/sum)




p <- ggplot(can.master, aes(x = loyalty_factor, y = percent)) +
  geom_col() +
  xlab("Candidate Party Loyalty Score") +
  scale_y_continuous(labels = label_comma()) +
  ylab("Perc. Candidates in Bin") +
  ggtitle("Candidates in Partisan Races") +
  theme_bw() +
  theme(text=element_text(size=15))

#Candidates in Non-Partisan Races
can.master <- data %>%
  filter(str_detect(code, "_np_")==TRUE & is.na(np_score)==FALSE) %>%
  select(code, clean_last, np_matches, np_score) %>%
  distinct() %>%
  mutate(counter=1,
         loyalty_factor=ifelse(np_score == -1, "-1", ""),
         loyalty_factor=ifelse(np_score > -1 & np_score <= -0.75, "-.99- -.75", loyalty_factor),
         loyalty_factor=ifelse(np_score > -0.75 & np_score <= -0.5, "-.74- -.51", loyalty_factor),
         loyalty_factor=ifelse(np_score > -0.5 & np_score <= -0.25, "-.49- -.25", loyalty_factor),
         loyalty_factor=ifelse(np_score > -0.25 & np_score < 0, "-.24- -.01", loyalty_factor),
         loyalty_factor=ifelse(np_score ==0, "0", loyalty_factor),
         loyalty_factor=ifelse(np_score > 0 & np_score <= 0.25, ".01-.24", loyalty_factor),
         loyalty_factor=ifelse(np_score > 0.25 & np_score <= 0.50, ".25-.49", loyalty_factor),
         loyalty_factor=ifelse(np_score > 0.50 & np_score <= 0.75, ".5-.74", loyalty_factor),
         loyalty_factor=ifelse(np_score > 0.75 & np_score < 1, ".75-.99", loyalty_factor),
         loyalty_factor=ifelse(np_score == 1, "1", loyalty_factor),
         loyalty_factor=factor(loyalty_factor, levels = c("-1", "-.99- -.75", "-.74- -.51", "-.49- -.25",
                                                          "-.24- -.01", "0", ".01-.24", ".25-.49", 
                                                          ".5-.74", ".75-.99", "1"), ordered = TRUE)) %>%
  group_by(loyalty_factor) %>%
  count(counter)  %>%
  ungroup()


sum<-sum(can.master$n)
can.master$percent <- 100* (can.master$n/sum)


np <- ggplot(can.master, aes(x = loyalty_factor, y = percent)) +
  geom_col() +
  xlab("Candidate Party Loyalty Score") +
  scale_y_continuous(labels = label_comma()) +
  ylab("Perc. Candidates in Bin") +
  ggtitle("Candidates in Non-Partisan Races") +
  theme_bw() +
  theme(text=element_text(size=15))


ggarrange(p, np,
          ncol = 1, nrow = 2,
          common.legend = FALSE)

rm(can.master, np, p, sum)

############################################

#Table A1: Descriptive Statistics: Votes Cast in Sub-State Elections Contested 
#by At Least One Democratic and One Republican Candidate

#Top pane

#Partisan Elections, Contested by at least 1 D and 1 R
des <- substate.data %>%
  filter(partisan==1 & num_rep>=1 & num_dem>=1) %>%
  select(incorrect_v, republican, perc_party_votes, inc_contested, np_spread, 
         ip_distance_fedstate, can_count)

stargazer(as.data.frame(des), digits=2, 
          covariate.labels=c("Dummy: Crossover Vote",
                             "Dummy: Voter is Republican",
                             "Voter Party Loyalty",
                             "Dummy: Incumbent-Contested Race",
                             "Max. Party Loyalty Dist. Btwn. Cands.",
                             "Loyalty Dist. Btwn. Voter and Selected Cand.",
                             "Number of Candidates"))

#Middle Pane

#Non-Partisan Elections, Contested by at least 1 D and 1 R
des <- substate.data %>%
  filter(partisan==0 & num_rep>=1 & num_dem>=1) %>%
  select(incorrect_v, republican, perc_party_votes, inc_contested, np_spread, 
         ip_distance_fedstate, can_count, ball_pos)

stargazer(as.data.frame(des), digits=2, 
          covariate.labels=c("Dummy: Crossover Vote",
                             "Dummy: Voter is Republican",
                             "Voter Party Loyalty",
                             "Dummy: Incumbent-Consted Race",
                             "Max. Party Loyalty Dist. Btwn. Cands.",
                             "Loyalty Dist. Btwn. Voter and Selected Cand.",
                             "Number of Candidates",
                             "Can. Ballot Position"))

#Bottom Pane

#Non-Partisan Elections, 1 D and 1 R
des <- substate.data.DR %>%
  filter(partisan==0) %>%
  select(incorrect_v, republican, perc_party_votes, inc_contested, np_spread, 
         ip_distance_fedstate, can_count, ball_pos)

stargazer(as.data.frame(des), digits=2, 
          covariate.labels=c("Dummy: Crossover Vote",
                             "Dummy: Voter is Republican",
                             "Voter Party Loyalty",
                             "Dummy: Incumbent-Consted Race",
                             "Max. Party Loyalty Dist. Btwn. Cands.",
                             "Loyalty Dist. Btwn. Voter and Selected Cand.",
                             "Number of Candidates",
                             "Can. Ballot Position"))

rm(des)

############################################

#Table A2
#Correlation coefficients for Table A2:  Correlations Between Voters' Partisan 
#Scores on Federal/State Ballot and Sub-State Ballot, by Partisanship of Office and Voter Party

#Races Where at Least 1 D and 1 R ran:

#Create voter-level data using only votes in party-contested races
voter.master <- substate.data %>% 
  arrange(id) %>% 
  filter (num_rep >= 1 & num_dem >= 1)

#Calculate partisan scores  
scores<- voter.master %>%
  filter(partisan==1) %>%
  group_by(id) %>% #Calculate scores within each voter 
  summarise_at(vars(p_vote),              
               list(p_contested_score = mean),na.rm=TRUE) 

voter.master <- left_join(voter.master, scores, by="id")

#NP scores
scores<- voter.master %>%
  filter(partisan==0) %>%
  group_by(id) %>% #Calculate scores within each voter 
  summarise_at(vars(np_party_est),              
               list(np_contested_score = mean),na.rm=TRUE) 

voter.master <- voter.master %>%
  left_join(scores, by="id") %>%
  group_by(id) %>%
  filter(row_number()==1) %>%
  filter(is.na(p_contested_score)==FALSE & is.na(np_contested_score)==FALSE) %>%
  select(id, voter_party_fedstate, p_ip_fedstate, p_contested_score, np_contested_score)

voter.d<-voter.master %>%
  filter(voter_party_fedstate==-1)
voter.r<-voter.master %>%
  filter(voter_party_fedstate==1)


cor(voter.d$p_ip_fedstate, voter.d$p_contested_score, use="complete.obs")
cor(voter.d$p_ip_fedstate, voter.d$np_contested_score, use="complete.obs")
cor(voter.d$p_contested_score, voter.d$np_contested_score, use="complete.obs")

cor(voter.r$p_ip_fedstate, voter.r$p_contested_score, use="complete.obs")
cor(voter.r$p_ip_fedstate, voter.r$np_contested_score, use="complete.obs")
cor(voter.r$p_contested_score, voter.r$np_contested_score, use="complete.obs")


#RESTRICTING TO 1 D/1 R RACES:

#Create voter-level data using only votes in party-contested races
voter.master <- substate.data.DR %>% arrange(id)

#Calculate partisan scores  
scores<- voter.master %>%
  filter(partisan==1) %>%
  group_by(id) %>% #Calculate scores within each voter 
  summarise_at(vars(p_vote),              
               list(p_contested_score = mean),na.rm=TRUE) 

voter.master <- left_join(voter.master, scores, by="id")

#NP scores
scores<- voter.master %>%
  filter(partisan==0) %>%
  group_by(id) %>% #Calculate scores within each voter 
  summarise_at(vars(np_party_est),              
               list(np_contested_score = mean),na.rm=TRUE) 

voter.master <- voter.master %>%
  left_join(scores, by="id") %>%
  group_by(id) %>%
  filter(row_number()==1) %>%
  filter(is.na(p_contested_score)==FALSE & is.na(np_contested_score)==FALSE) %>%
  select(id, voter_party_fedstate, p_ip_fedstate, p_contested_score, np_contested_score)

voter.d<-voter.master %>%
  filter(voter_party_fedstate==-1)
voter.r<-voter.master %>%
  filter(voter_party_fedstate==1)


cor(voter.d$p_ip_fedstate, voter.d$p_contested_score, use="complete.obs")
cor(voter.d$p_ip_fedstate, voter.d$np_contested_score, use="complete.obs")
cor(voter.d$p_contested_score, voter.d$np_contested_score, use="complete.obs")

cor(voter.r$p_ip_fedstate, voter.r$p_contested_score, use="complete.obs")
cor(voter.r$p_ip_fedstate, voter.r$np_contested_score, use="complete.obs")
cor(voter.r$p_contested_score, voter.r$np_contested_score, use="complete.obs")

rm(voter.d, voter.r, scores, voter.master)

############################################

#Figure A3: Percent of Crossover Votes Cast by Partisan Voters in Sub-State Partisan and
#Nonpartisan Elections Contested by One Democrat and One Republican, by Candidate Incumbency

#Incumbent-contested races, R voter/R incumbent
np <- substate.data.DR %>%
  filter(partisan==1) %>%
  filter(inc_contested==1) %>%
  filter(republican==1) %>%
  filter(inc_party==1) 
#Obs provide the count

#Calculate Rate By Office
r.r.rd.means <- np %>% 
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  mutate(contested="Rep. Incumbent",
         voter_p="Rep.")

#Incumbent-contested races, D voter/R incumbent
np <- substate.data.DR %>%
  filter(partisan==1) %>%
  filter(inc_contested==1) %>%
  filter(republican==0) %>%
  filter(inc_party==1) 
#Obs provide the count

#Calculate Rate By Office
d.r.rd.means <- np %>% 
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  mutate(contested="Rep. Incumbent",
         voter_p="Dem.")

#Incumbent-contested races, R voter/D incumbent
np <- substate.data.DR %>%
  filter(partisan==1) %>%
  filter(inc_contested==1) %>%
  filter(republican==1) %>%
  filter(inc_party==-1) 
#Obs provide the count

#Calculate Rate By Office
r.d.rd.means <- np %>% 
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  mutate(contested="Dem. Incumbent",
         voter_p="Rep.")

#Incumbent-contested races, D voter/D incumbent
np <- substate.data.DR %>%
  filter(partisan==1) %>%
  filter(inc_contested==1) %>%
  filter(republican==0) %>%
  filter(inc_party==-1) 
#Obs provide the count

#Calculate Rate By Office
d.d.rd.means <- np %>% 
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  mutate(contested="Dem. Incumbent",
         voter_p="Dem.")

#Open Seat races, R voter
np <- substate.data.DR %>%
  filter(partisan==1) %>%
  filter(inc_contested==0) %>%
  filter(republican==1) 
#Obs provide the count

#Calculate Rate By Office
r.o.rd.means <- np %>% 
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  mutate(contested="Open Seat",
         voter_p="Rep.")

#Open Seat races, D voter
np <- substate.data.DR %>%
  filter(partisan==1) %>%
  filter(inc_contested==0) %>%
  filter(republican==0) 
#Obs provide the count

#Calculate Rate By Office
d.o.rd.means <- np %>% 
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  mutate(contested="Open Seat",
         voter_p="Dem.")

#Combine partisan means into one data frame
contested.means<-rbind(r.r.rd.means, r.d.rd.means, r.o.rd.means,
                       d.r.rd.means, d.d.rd.means, d.o.rd.means)
contested.means<- contested.means %>%
  mutate(contested=factor(contested, levels=c("Rep. Incumbent",
                                              "Dem. Incumbent",
                                              "Open Seat"
                                              )))

#Draw the Partisan figure
p.panel <- ggplot(contested.means, aes(x = contested, y = ic, fill=voter_p)) +
  geom_bar(stat = "identity",
           alpha=0.7, position="dodge") +
  scale_fill_manual(name="Voter Party", values = c("blue", "red")) +
  xlab("") +
  scale_y_continuous(limits=c(0,.52), breaks=c(.1,.2,.3,.4,.5,.6), labels = scales::percent_format(accuracy = 1)) +
  ylab("Perc. Crossover Voting") +
  ggtitle("County/Local Partisan Elections") +
  theme_bw() +
  theme(text=element_text(size=15), legend.position="bottom")


###Non-Partisan Pane:
#Sub-State Non-Partisan Races:

#####Votes cast by partisan voters in non-partisan races CONTESTED BY EXACTLY 1 D and 1 R

#Incumbent-contested races, R voter/R incumbent
np <- substate.data.DR %>%
  filter(partisan==0) %>%
  filter(inc_contested==1) %>%
  filter(republican==1) %>%
  filter(inc_party==1) 
#Obs provide the count

#Calculate Rate By Office
r.r.rd.means <- np %>% 
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  mutate(contested="Rep. Incumbent",
         voter_p="Rep.")

#Incumbent-contested races, D voter/R incumbent
np <- substate.data.DR %>%
  filter(partisan==0) %>%
  filter(inc_contested==1) %>%
  filter(republican==0) %>%
  filter(inc_party==1) 
#Obs provide the count

#Calculate Rate By Office
d.r.rd.means <- np %>% 
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  mutate(contested="Rep. Incumbent",
         voter_p="Dem.")

#Incumbent-contested races, R voter/D incumbent
np <- substate.data.DR %>%
  filter(partisan==0) %>%
  filter(inc_contested==1) %>%
  filter(republican==1) %>%
  filter(inc_party==-1) 
#Obs provide the count

#Calculate Rate By Office
r.d.rd.means <- np %>% 
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  mutate(contested="Dem. Incumbent",
         voter_p="Rep.")

#Incumbent-contested races, D voter/D incumbent
np <- substate.data.DR %>%
  filter(partisan==0) %>%
  filter(inc_contested==1) %>%
  filter(republican==0) %>%
  filter(inc_party==-1) 
#Obs provide the count

#Calculate Rate By Office
d.d.rd.means <- np %>% 
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  mutate(contested="Dem. Incumbent",
         voter_p="Dem.")

#Open Seat races, R voter
np <- substate.data.DR %>%
  filter(partisan==0) %>%
  filter(inc_contested==0) %>%
  filter(republican==1) 
#Obs provide the count

#Calculate Rate By Office
r.o.rd.means <- np %>% 
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  mutate(contested="Open Seat",
         voter_p="Rep.")

#Open Seat races, D voter
np <- substate.data.DR %>%
  filter(partisan==0) %>%
  filter(inc_contested==0) %>%
  filter(republican==0) 
#Obs provide the count

#Calculate Rate By Office
d.o.rd.means <- np %>% 
  summarise_at(vars(incorrect_v),              
               list(ic = mean),na.rm=TRUE) %>%
  mutate(contested="Open Seat",
         voter_p="Dem.")

#Combine partisan means into one data frame
contested.means<-rbind(r.r.rd.means, r.d.rd.means, r.o.rd.means,
                       d.r.rd.means, d.d.rd.means, d.o.rd.means)
contested.means<- contested.means %>%
  mutate(contested=factor(contested, levels=c("Rep. Incumbent",
                                              "Dem. Incumbent",
                                              "Open Seat"
  )))


#Draw the Non-Partisan figure
np.panel <- ggplot(contested.means, aes(x = contested, y = ic, fill=voter_p)) +
  geom_bar(stat = "identity",
           alpha=0.7, position="dodge") +
  scale_fill_manual(name="Voter Party", values = c("blue", "red")) +
  xlab("") +
  scale_y_continuous(limits=c(0,.52), breaks=c(.1,.2,.3,.4,.5,.6), labels = scales::percent_format(accuracy = 1)) +
  ylab("Perc. Crossover Voting") +
  ggtitle("County/Local Non-Partisan Elections") +
  theme_bw() +
  theme(text=element_text(size=15), legend.position="bottom")


ggarrange(p.panel, np.panel,
          ncol = 2, nrow = 1,
          common.legend = TRUE, legend="bottom")

rm(np, contested.means, np.panel, p.panel, d.d.rd.means, d.o.rd.means,
   d.r.rd.means, r.d.rd.means, r.o.rd.means, r.r.rd.means)

############################################

#Results from Models

############################################

#Before making tables and figures, we first fit the models

#Linear Probability Models

#Dems

#Must subset data for SE clustering to work, using the method below
model.data <- substate.model.contested %>%
  filter(partisan==0 & republican==0)

lpm.demo.1 <- lm(incorrect_v ~  perc_party_votes 
                             + same_party_inc + out_party_inc +
                               num_dem + num_rep +
                               np_spread + coparty_first_ball + 
                               b_d_b_r + b_d_w_r + b_w_r_b_d + b_w_d_w_r +
                               f_d_f_r + m_d_f_r + f_d_m_r + m_f_r_f_d + m_f_d_m_r + 
                               m_f_d_f_r + clean_code,
                      data=model.data)

cl.cov1 <- cluster.vcov(lpm.demo.1, model.data$id) # cluster-robust SEs 
cl.robust.se.1 <- sqrt(diag(cl.cov1))
#cl.wald1 <- waldtest(lpm.demo.1, vcov = cl.cov1)

#Reps

model.data <- substate.model.contested %>%
  filter(partisan==0 & republican==1)

lpm.demo.2 <- lm(incorrect_v ~  perc_party_votes 
                 + same_party_inc + out_party_inc +
                   num_dem + num_rep +
                   np_spread + coparty_first_ball + 
                   b_d_b_r + b_d_w_r + b_w_r_b_d + b_w_d_w_r +
                   f_d_f_r + m_d_f_r + f_d_m_r + m_f_r_f_d + m_f_d_m_r + 
                   m_f_d_f_r + clean_code,
                 data=model.data)

cl.cov2 <- cluster.vcov(lpm.demo.2, model.data$id) # cluster-robust SEs
cl.robust.se.2 <- sqrt(diag(cl.cov2))



#Partisan Races


#Dems
model.data <- substate.model.contested %>%
  filter(partisan==1 & republican==0)

lpm.demo.3 <- lm(incorrect_v ~  perc_party_votes 
                 + same_party_inc + out_party_inc +
                   np_spread +  can_count +
                   b_d_w_r + b_d_b_r + 
                   m_d_f_r + f_d_m_r + f_d_f_r +
                   clean_code, 
                 data=model.data)
cl.cov3 <- cluster.vcov(lpm.demo.3, model.data$id) # cluster-robust SEs
cl.robust.se.3 <- sqrt(diag(cl.cov3))



#Reps
model.data <- substate.model.contested %>%
  filter(partisan==1 & republican==1)

lpm.demo.4 <- lm(incorrect_v ~  perc_party_votes 
                 + same_party_inc + out_party_inc +
                   np_spread + can_count +
                   b_d_w_r + b_d_b_r + 
                   m_d_f_r + f_d_m_r + f_d_f_r +
                   clean_code, 
                 data=model.data)

cl.cov4 <- cluster.vcov(lpm.demo.4, model.data$id) # cluster-robust SEs
cl.robust.se.4 <- sqrt(diag(cl.cov4))

############################################

#Figure 4: Coefficient Estimates from Linear Probability Models of Crossover Voting in
#Partisan and Nonpartisan Elections

#Coefficient Plot: Non-Partisan

#Dems
m1_tidy <- tidy(lpm.demo.1) %>%
  mutate(model="Dem. Voters") %>%
  filter(term!="clean_codemayor")

#Reps
m2_tidy <- tidy(lpm.demo.2) %>%
  mutate(model="Rep. Voters") %>%
  filter(term!="clean_codemayor")


all_models<-rbind(m1_tidy, m2_tidy)

np<-dwplot(all_models,
       vline = geom_vline(
         xintercept = 0,
         colour = "red",
         linetype = 2
       ),
       dot_args = list(aes(colour = model), size=1.8), 
       whisker_args = list(size = .5),
       vars_order = c("perc_party_votes", "same_party_inc", "out_party_inc", 
                      "num_dem", "num_rep", 
                      "np_spread", "coparty_first_ball",
                      "b_d_w_r", "b_d_b_r",
                      "b_w_r_b_d", "b_w_d_w_r",
                      "m_d_f_r","f_d_m_r", "f_d_f_r",
                      "m_f_r_f_d", "m_f_d_m_r", "m_f_d_f_r"),
       model_order = c("Dem. Voters","Rep. Voters")
) %>% 
  relabel_predictors(
    c(
      perc_party_votes= "Voter's Party Loyalty Score",
      same_party_inc= "Same-Party Incumbent Running",
      out_party_inc= "Out-Party Incumbent Running",
      num_dem = "Number of Democrats in Race",
      num_rep = "Number of Republicans in Race",
      np_spread = "Max. Candidate Party Loyalty Distance",
      coparty_first_ball = "Copartisan is Listed First on Ballot",
      b_d_w_r = "Black D/White R",
      b_d_b_r = "Black D/Black R",
      b_w_r_b_d = "Black and White R/Black D",
      b_w_d_w_r = "Black and White D/White R",
      m_d_f_r = "Male D/Female R",
      f_d_m_r = "Female D/Male R",
      f_d_f_r = "Female D/Female R",
      m_f_r_f_d = "Male and Female R/Female D", 
      m_f_d_m_r = "Male and Female D/Male R",  
      m_f_d_f_r = "Male and Female D/Female R")) +
  scale_color_manual(values = c('red', 'blue')) +
  theme_bw() +  
  ggtitle("County/Municipal Non-Partisan Elections") +
  xlab("Coefficient Estimate") + ylab("") +
  theme(
    legend.position = c(.73, 0.28),
    legend.justification = c(0, 0),
    legend.background = element_rect(colour = "grey80"),
    legend.title = element_blank(),
    text=element_text(size=12)
  ) 

#Coefficient Plot: Partisan

#Dems
m1_tidy <- tidy(lpm.demo.3) %>%
  mutate(model="Dem. Voters") %>%
  filter(term!="clean_codemayor")

#Reps
m2_tidy <- tidy(lpm.demo.4) %>%
  mutate(model="Rep. Voters") %>%
  filter(term!="clean_codemayor")


all_models<-rbind(m1_tidy, m2_tidy)

p<-dwplot(all_models,
       vline = geom_vline(
         xintercept = 0,
         colour = "red",
         linetype = 2
       ),
       dot_args = list(aes(colour = model), size=1.8), 
       whisker_args = list(size = .5),
       vars_order = c("perc_party_votes", "same_party_inc", "out_party_inc", 
                      "np_spread", "can_count",
                      "b_d_w_r", "b_d_b_r",
                      "m_d_f_r","f_d_m_r", "f_d_f_r"),
       model_order = c("Dem. Voters","Rep. Voters")
) %>% 
  relabel_predictors(
    c(
      perc_party_votes= "Voter's Party Loyalty Score",
      same_party_inc= "Same-Party Incumbent Running",
      out_party_inc= "Out-Party Incumbent Running",
      np_spread = "Max. Candidate Party Loyalty Distance",
      can_count= "Number of Candidates in Race",
      b_d_w_r = "Black D/White R",
      b_d_b_r = "Black D/Black R",
      m_d_f_r = "Male D/Female R",
      f_d_m_r = "Female D/Male R",
      f_d_f_r = "Female D/Female R"
    )) +
  scale_color_manual(values = c('red', 'blue')) +
  theme_bw() +  
  ggtitle("County/Municipal Partisan Elections") +
  xlab("Coefficient Estimate") + ylab("") +
  theme(
    legend.position = c(.73, 0.03),
    legend.justification = c(0, 0),
    legend.background = element_rect(colour = "grey80"),
    legend.title = element_blank(),
    text=element_text(size=12)
  ) 

ggarrange(p, np,
        ncol = 1, nrow = 2,
         common.legend = FALSE)

ggsave(filename = "modelplot.png",width = 8, height = 8.4, dpi = 300)

rm(np, p)
###############################################################

#Table A3

#Tabling these models: This table appears in paper SM
#Table A3: Coefficients and Clustered Robust Standard Errors: Linear Probability 
#Model of Crossover Voting


#We are including clustered SEs
stargazer(lpm.demo.1, lpm.demo.2, lpm.demo.3, lpm.demo.4,
          se = list(cl.robust.se.1, cl.robust.se.2, cl.robust.se.3, cl.robust.se.4),
          order = c("perc_party_votes", "same_party_inc", "out_party_inc",
                    "num_dem", "num_rep", "can_count", "np_spread", "coparty_first_ball",
                    "b_d_w_r", "b_d_b_r", "b_w_r_b_d", "b_w_d_w_r", "m_f_d_m_r", 
                    "m_f_d_f_r", "m_f_r_f_d", "f_d_f_r", "m_d_f_r",
                    "f_d_m_r"),
          #type="html", 
          style="apsr",
          #title="LPM Coefficients and Standard Errors: Correlates of Voting for a Non-Partisan Candidate of the Opposite Party in Party-Contested Non-Partisan Races",
          omit="clean_code",
          dep.var.labels=c("P(Vote for Opp. Party Cand.)"),
          column.labels=c("\\multirow{2}{2.5 cm}{Democratic Voters}",
                          "\\multirow{2}{2.5cm}{Republican Voters}"),
          #column.separate = c(2,2,2,2,2),
          covariate.labels=c("Voter's Party Loyalty Score",
                             "Same-Party Incumbent Running",
                             "Out-Party Incumbent Running",
                             "Number of Democrats in Race",
                             "Number of Republicans in Race",
                             "Number of Candidates in Race",
                             "Max. Candidate Party Loyalty Distance",
                             "Copartisan is Listed First on Ballot",
                             "Black D/White R",
                             "Black D/Black R",
                             "Black D/Black and White R",
                             "Black and White D/White R",
                             "Male and Female D/Male R",
                             "Male and Female D/Female R",
                             "Male and Female R/Female D",
                             "Female D/Female R",
                             "Male D/Female R",
                             "Female D/Male R"), 
          digits=2,
          keep.stat=c("n","rsq"))

###############################################################

#Figure A4: Coefficient Estimates from Linear Probability Models of Crossover Voting in
#Partisan and Nonpartisan Elections, Restricted to Voters with Perfect Party Loyalty Scores


#Here we re-fit the model on data from voters with perfect partisan scores

#Dems

#Must subset data for clustering to work, using the method below
model.data <- substate.model.contested.perfect %>%
  filter(partisan==0 & republican==0)

lpm.demo.1 <- lm(incorrect_v ~  same_party_inc + out_party_inc +
                   num_dem + num_rep +
                   np_spread + coparty_first_ball + 
                   b_d_b_r + b_d_w_r + b_w_r_b_d + b_w_d_w_r +
                   f_d_f_r + m_d_f_r + f_d_m_r + m_f_r_f_d + m_f_d_m_r + 
                   m_f_d_f_r + clean_code,
                 data=model.data)

cl.cov1 <- cluster.vcov(lpm.demo.1, model.data$id) # cluster-robust SEs 
cl.robust.se.1 <- sqrt(diag(cl.cov1))
#cl.wald1 <- waldtest(lpm.demo.1, vcov = cl.cov1)

#Reps

model.data <- substate.model.contested.perfect %>%
  filter(partisan==0 & republican==1)

lpm.demo.2 <- lm(incorrect_v ~  same_party_inc + out_party_inc +
                   num_dem + num_rep +
                   np_spread + coparty_first_ball + 
                   b_d_b_r + b_d_w_r + b_w_r_b_d + b_w_d_w_r +
                   f_d_f_r + m_d_f_r + f_d_m_r + m_f_r_f_d + m_f_d_m_r + 
                   m_f_d_f_r + clean_code,
                 data=model.data)

cl.cov2 <- cluster.vcov(lpm.demo.2, model.data$id) # cluster-robust SEs
cl.robust.se.2 <- sqrt(diag(cl.cov2))



#Partisan Races


#Dems
model.data <- substate.model.contested.perfect %>%
  filter(partisan==1 & republican==0)

lpm.demo.3 <- lm(incorrect_v ~  same_party_inc + out_party_inc +
                   np_spread +  can_count +
                   b_d_w_r + b_d_b_r + 
                   m_d_f_r + f_d_m_r + f_d_f_r +
                   clean_code, 
                 data=model.data)
cl.cov3 <- cluster.vcov(lpm.demo.3, model.data$id) # cluster-robust SEs
cl.robust.se.3 <- sqrt(diag(cl.cov3))



#Reps
model.data <- substate.model.contested.perfect %>%
  filter(partisan==1 & republican==1)

lpm.demo.4 <- lm(incorrect_v ~  same_party_inc + out_party_inc +
                   np_spread + can_count +
                   b_d_w_r + b_d_b_r + 
                   m_d_f_r + f_d_m_r + f_d_f_r +
                   clean_code, 
                 data=model.data)

cl.cov4 <- cluster.vcov(lpm.demo.4, model.data$id) # cluster-robust SEs
cl.robust.se.4 <- sqrt(diag(cl.cov4))


#Coefficient Plot: Non-Partisan

#Dems
m1_tidy <- tidy(lpm.demo.1) %>%
  mutate(model="Dem. Voters") %>%
  filter(term!="clean_codemayor")

#Reps
m2_tidy <- tidy(lpm.demo.2) %>%
  mutate(model="Rep. Voters") %>%
  filter(term!="clean_codemayor")


all_models<-rbind(m1_tidy, m2_tidy)

np<-dwplot(all_models,
           vline = geom_vline(
             xintercept = 0,
             colour = "red",
             linetype = 2
           ),
           dot_args = list(aes(colour = model), size=1.8), 
           whisker_args = list(size = .5),
           vars_order = c("same_party_inc", "out_party_inc", 
                          "num_dem", "num_rep", 
                          "np_spread", "coparty_first_ball",
                          "b_d_w_r", "b_d_b_r",
                          "b_w_r_b_d", "b_w_d_w_r",
                          "m_d_f_r","f_d_m_r", "f_d_f_r",
                          "m_f_r_f_d", "m_f_d_m_r", "m_f_d_f_r"),
           model_order = c("Dem. Voters","Rep. Voters")
) %>% 
  relabel_predictors(
    c(
      same_party_inc= "Same-Party Incumbent Running",
      out_party_inc= "Out-Party Incumbent Running",
      num_dem = "Number of Democrats in Race",
      num_rep = "Number of Republicans in Race",
      np_spread = "Max. Candidate Party Loyalty Distance",
      coparty_first_ball = "Copartisan is Listed First on Ballot",
      b_d_w_r = "Black D/White R",
      b_d_b_r = "Black D/Black R",
      b_w_r_b_d = "Black and White R/Black D",
      b_w_d_w_r = "Black and White D/White R",
      m_d_f_r = "Male D/Female R",
      f_d_m_r = "Female D/Male R",
      f_d_f_r = "Female D/Female R",
      m_f_r_f_d = "Male and Female R/Female D", 
      m_f_d_m_r = "Male and Female D/Male R",  
      m_f_d_f_r = "Male and Female D/Female R")) +
  scale_color_manual(values = c('red', 'blue')) +
  theme_bw() +  
  ggtitle("County/Municipal Non-Partisan Elections") +
  xlab("Coefficient Estimate") + ylab("") +
  theme(
    legend.position = c(.73, 0.28),
    legend.justification = c(0, 0),
    legend.background = element_rect(colour = "grey80"),
    legend.title = element_blank(),
    text=element_text(size=12)
  ) 

#Coefficient Plot: Partisan

#Dems
m1_tidy <- tidy(lpm.demo.3) %>%
  mutate(model="Dem. Voters") %>%
  filter(term!="clean_codemayor")

#Reps
m2_tidy <- tidy(lpm.demo.4) %>%
  mutate(model="Rep. Voters") %>%
  filter(term!="clean_codemayor")


all_models<-rbind(m1_tidy, m2_tidy)

p<-dwplot(all_models,
          vline = geom_vline(
            xintercept = 0,
            colour = "red",
            linetype = 2
          ),
          dot_args = list(aes(colour = model), size=1.8), 
          whisker_args = list(size = .5),
          vars_order = c("same_party_inc", "out_party_inc", 
                         "np_spread", "can_count",
                         "b_d_w_r", "b_d_b_r",
                         "m_d_f_r","f_d_m_r", "f_d_f_r"),
          model_order = c("Dem. Voters","Rep. Voters")
) %>% 
  relabel_predictors(
    c(
      same_party_inc= "Same-Party Incumbent Running",
      out_party_inc= "Out-Party Incumbent Running",
      np_spread = "Max. Candidate Party Loyalty Distance",
      can_count= "Number of Candidates in Race",
      b_d_w_r = "Black D/White R",
      b_d_b_r = "Black D/Black R",
      m_d_f_r = "Male D/Female R",
      f_d_m_r = "Female D/Male R",
      f_d_f_r = "Female D/Female R"
    )) +
  scale_color_manual(values = c('red', 'blue')) +
  theme_bw() +  
  ggtitle("County/Municipal Partisan Elections") +
  xlab("Coefficient Estimate") + ylab("") +
  theme(
    legend.position = c(.73, 0.03),
    legend.justification = c(0, 0),
    legend.background = element_rect(colour = "grey80"),
    legend.title = element_blank(),
    text=element_text(size=12)
  ) 

ggarrange(p, np,
          ncol = 1, nrow = 2,
          common.legend = FALSE)

ggsave(filename = "modelplot_partisans.png",width = 8, height = 8.4, dpi = 300)

###############################################################

#Table A4: Actual and Predicted Votes for Democratic and Republican Candidates in
#Nonpartisan Elections Where One Democrat and One Republican Ran

#This is a back of the envelope calculation in 1 R/1 D races
#We want to know whether we think results would have flipped.

#First let's figure out the baseline rate of crossover votes for Democratic and Republican
#voters in substate partisan races contested by a Democrat and Republican

#Baseline rate for Democrats: Numerator
substate.data.DR %>%
  filter(partisan==1 & republican==0 & one_d_one_r==1)  %>%
  summarise(total_iv = sum(incorrect_v)) 

#Denominator
substate.data.DR %>%
  filter(partisan==1 & republican==0 & one_d_one_r==1)  %>%
  mutate(counter=1) %>%
  summarise(total_v = sum(counter)) 

dem.rate <- 23337/350338
#Democratic rate is 6.6%

#Baseline rate for Republicans: Numerator
substate.data.DR %>%
  filter(partisan==1 & republican==1 & one_d_one_r==1)  %>%
  summarise(total_iv = sum(incorrect_v)) 

#Denominator
substate.data.DR %>%
  filter(partisan==1 & republican==1 & one_d_one_r==1)  %>%
  mutate(counter=1) %>%
  summarise(total_v = sum(counter)) 

rep.rate <- 19275/402517
#Republican rate is 4.78%


########################Input Actual Vote: Democrats in Nonpartisan races

#Total the actual vote counts by election from voters of each party

#This will count actual recorded votes for Dem and Rep candidates

#First Subset the data to Democratic voters
actual_d <- substate.model.contested %>%
  filter(partisan==0 & republican==0 & one_d_one_r==1) %>% 
  mutate(counter=1,
         clean_last=ifelse(np_party_est==-1, "d", "r"),
         count_code=paste(code,clean_last, sep="%")) %>%
  group_by(count_code) %>%
  summarise(actual_dv = sum(counter)) 

#Now Republican voters
actual_r <- substate.model.contested %>%
  filter(partisan==0 & republican==1 & one_d_one_r==1) %>% 
  mutate(counter=1,
         clean_last=ifelse(np_party_est==-1, "d", "r"),
         count_code=paste(code,clean_last, sep="%")) %>%
  group_by(count_code) %>%
  summarise(actual_rv = sum(counter)) 

###################Merge actual counts for Dem and Rep voters

actual <- actual_d %>%
  left_join(actual_r, by="count_code") 

rm(actual_d, actual_r)

office.can<-as_tibble(str_split_fixed(actual$count_code, "%", 2))

actual$office <- office.can$V1
actual$candidate <- office.can$V2
rm(office.can)

actual <- actual %>%
  select(-count_code) %>%
  pivot_wider(names_from="candidate",
              names_prefix="can_",
              values_from=c("actual_dv","actual_rv")) %>%
  mutate( pred_dv_can_d = round((actual_dv_can_d + actual_dv_can_r)*(1-dem.rate), digits=0), #predicted votes for dem candidates from dem voters, calculated by totaling the number of votes cast and then applying the inverse of the partisan crossover rate
          pred_dv_can_r = round((actual_dv_can_d + actual_dv_can_r)*(dem.rate), digits=0), #predicted votes for rep candidates from dem voters, calculated by totaling the number of votes cast and then applying the partisan crossover rate
          pred_rv_can_r = round((actual_rv_can_d + actual_rv_can_r)*(1-rep.rate), digits=0), #predicted votes for rep candidates from rep voters
          pred_rv_can_d = round((actual_rv_can_d + actual_rv_can_r)*(rep.rate), digits=0), #predicted votes for dem candidates from rep voters
          
          d_actual= actual_dv_can_d + actual_rv_can_d,
          d_pred= pred_dv_can_d + pred_rv_can_d,
          r_actual= actual_dv_can_r + actual_rv_can_r,
          r_pred= pred_dv_can_r + pred_rv_can_r,
          d_win_actual=ifelse(d_actual > r_actual, 1,0),
          d_win_pred=ifelse(d_pred > r_pred, 1,0),
          disagree=ifelse(d_win_actual!=d_win_pred, 1, 0),
          office=str_replace_all(office, "_np_", ""),
          office=ifelse(office=="Aiken136_boe", "Aiken Bd. of Ed.", office),
          office=ifelse(office=="Charleston130_boe", "Charleston Bd. of Ed.", office),
          office=ifelse(office=="Florence187_boe", "Florence Bd. of Ed.", office),
          office=ifelse(office=="Greenwood119_boe", "Greenwood Bd. of Ed. 1", office),
          office=ifelse(office=="Greenwood135_boe", "Greenwood Bd. of Ed. 2", office),
          office=ifelse(office=="Greenwood13_mayor", "Greenwood Mayor", office),
          office=ifelse(office=="Lancaster151_boe", "Lancaster Bd. of Ed.", office),
          office=ifelse(office=="Laurens212_boe", "Laurens Bd. of Ed.", office),
          office=ifelse(office=="Oconee140_boe", "Oconee Bd. of Ed.", office),
          office=ifelse(office=="Orangeburg136_boe", "Orangeburg Bd. of Ed.", office),
          office=ifelse(office=="Richland219_boe", "Richland Bd. of Ed.", office),
          office=ifelse(office=="Sumter136_boe", "Sumter Bd. of Ed.", office),
          office=ifelse(office=="Union167_boe", "Union Bd. of Ed. 1", office),
          office=ifelse(office=="Union171_boe", "Union Bd. of Ed. 2", office),
          'Different Outcome'=ifelse(disagree==1, "Yes", "No"),
          ) %>%
  select(office, d_actual:r_pred, 'Different Outcome') %>%
  rename(Office=office,
         'Predicted Vote for Dem.'=d_pred,
         'Predicted Vote for Rep.'=r_pred,
         'Actual Vote for Dem.'=d_actual,
         'Actual Vote for Rep.'=r_actual)

#Make Table A4
print(xtable(actual, digits=0), 
      include.rownames=FALSE)





